//
// Display a Mandelbrot set
//

// Uncomment the following line to use user-specified input values
// #define USER_INPUT

//#ifdef USER_INPUT
//// Include the C++ iostreams classes for use when collecting user input
//#include <iostream>
//#endif 

#include "Angel.h"
#include "bmpread.h"

typedef Angel::vec4 point4;
typedef Angel::vec4 color4;

// Use the C++ standard complex-number class.  It's a C++ template, so
//   we create a typedef of simplify our code.
#include <complex>
typedef std::complex<float>  Complex;

inline float Random() { return float(rand())/RAND_MAX; }

using namespace std;

GLuint Projection;

/* default data*/
/* can enter other values via command line arguments */

#define CENTERX -0.5
#define CENTERY 0.5
#define HEIGHT 0.5
#define WIDTH 0.5
#define MAX_ITER 100

/* N x M array to be generated */

#define N 512
#define M 512

float height = HEIGHT;          /* size of window in complex plane */
float width = WIDTH;


int n = N;
int m = M;
bool initDisplay = false;
void rebind();
void setup_O();
void setup_L();
void setup_N();
void setupShader(int shaderIndex, const char * fShader);

void setupCommon(GLuint programId);

bmpread_t bitmap;

GLuint program_O;
GLuint program_L;
GLuint program_N;
GLuint programs[9];

const char* fShaders[9] = {
	"fShaderO.glsl",
	"fShaderL.glsl",
	"fShaderN.glsl",
	"fShaderD.glsl",
	"fShaderE.glsl",
	"fShaderT.glsl",
	"fShaderW.glsl",
	"fShaderP.glsl",
	"fShaderS.glsl",
};

enum IMG {ORIGINAL, LUMINANCE, NEGATIVE, DETECTION, EMBOSSING, TOON, TWIRL, RIPPLE, SPHERICAL};

static  GLuint  texture = 0;
//----------------------------------------------------------------------------

point4 points[6] = {
	point4( 0.0, 0.0, 0.0, 1.0 ),
	point4( 0.0, 1.0, 0.0, 1.0 ),
	point4( 1.0, 1.0, 0.0, 1.0 ),
	point4( 1.0, 1.0, 0.0, 1.0 ),
	point4( 1.0, 0.0, 0.0, 1.0 ),
	point4( 0.0, 0.0, 0.0, 1.0 )
    };

vec2 texCoords[6] = {
	vec2(0.0,0.0),
	vec2(0.0,1.0),
	vec2(1.0,1.0),
	vec2(1.0,1.0),
	vec2(1.0,0.0),
	vec2(0.0,0.0),
};
GLuint buffer;


void
init( void )
{
	//load picture
	printf("Loading usain_bolt.bmp\n");

	if(!bmpread("usain_bolt.bmp", 0, &bitmap))
	{
		fprintf(stderr, "%s:error loading bitmap file\n", "usain_bolt.bmp");
		exit(1);
	}

    glActiveTexture( GL_TEXTURE0 );
    glGenTextures( 1, &texture );
    glBindTexture( GL_TEXTURE_2D, texture );

	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, bitmap.width, bitmap.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap.rgb_data );
	bmpread_free(&bitmap);


    // Create a vertex array object
    GLuint vao;
    glGenVertexArrays( 1, &vao );
    glBindVertexArray( vao );

    // Create and initialize a buffer object
    
    glGenBuffers( 1, &buffer );
    glBindBuffer( GL_ARRAY_BUFFER, buffer );

	glBufferData( GL_ARRAY_BUFFER, sizeof(points)+sizeof(texCoords), NULL, GL_STATIC_DRAW );
	glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof(points),points);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(points),sizeof(texCoords),texCoords);

	//init shaders
	for(int index = 0;index<9;index++)
	{
		setupShader(index,fShaders[index]);
	}

    glClearColor( 0.0, 0.0, 0.0, 1.0 );
}

void setupCommon(GLuint programId)
{
	// set up vertex arrays
    GLuint vPosition = glGetAttribLocation( programId, "vPosition" );
    glEnableVertexAttribArray( vPosition );
    glVertexAttribPointer( vPosition, 4, GL_FLOAT, GL_FALSE, 0,
			   BUFFER_OFFSET(0) );

    // Since our texture coordinates match our vertex positions, we
    //   can reuse the position data for our texture coordinates.
    GLuint vTexCoord = glGetAttribLocation( programId, "vTexCoord" ); 
    glEnableVertexAttribArray( vTexCoord );
    glVertexAttribPointer( vTexCoord, 2, GL_FLOAT, GL_FALSE, 0,
			   BUFFER_OFFSET(sizeof(points)) );

	Projection = glGetUniformLocation( programId, "Projection" );
	glViewport( 0, 0, width, height );
    mat4 projection = Ortho( 0.0, 1.0, 0.0, 1.0, -1.0, 1.0 );
    glUniformMatrix4fv( Projection, 1, GL_TRUE, projection );
    

    // Set our texture samples to the active texture unit
    glUniform1i( glGetUniformLocation(programId, "texture"), 0 );
    glBindTexture(GL_TEXTURE_2D, texture);
}

void setup_O()
{
	// Load shaders and use the resulting shader program
    program_O = InitShader( "vshader.glsl", "fshaderO.glsl" );
    glUseProgram( program_O );
	
    setupCommon( program_O );
}

void setup_L()
{
	// Load shaders and use the resulting shader program
    program_L = InitShader( "vshader.glsl", "fshaderL.glsl" );
    glUseProgram( program_L );

    setupCommon( program_L );
}

void setup_N()
{
	program_N = InitShader( "vshader.glsl", "fshaderN.glsl" );
    glUseProgram( program_L );
    setupCommon( program_L );
}

void setupShader(int shaderIndex, const char* fShader)
{
	programs[shaderIndex] = InitShader( "vshader.glsl",fShader);
	glUseProgram( programs[shaderIndex] );
    setupCommon( programs[shaderIndex] );
}

//----------------------------------------------------------------------------

void
reshape( int width, int height )
{
    glViewport( 0, 0, width, height );
    mat4 projection = Ortho( 0.0, 1.0, 0.0, 1.0, -1.0, 1.0 );
    glUniformMatrix4fv( Projection, 1, GL_TRUE, projection );

}

void rebind()
{
	glActiveTexture( GL_TEXTURE0 );
		glBindTexture( GL_TEXTURE_2D, texture );
		glBindBuffer( GL_ARRAY_BUFFER, buffer );
		glBufferData( GL_ARRAY_BUFFER, sizeof(points)+sizeof(texCoords), NULL, GL_STATIC_DRAW );
		glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof(points),points);
		glBufferSubData(GL_ARRAY_BUFFER, sizeof(points),sizeof(texCoords),texCoords);
}
//----------------------------------------------------------------------------

void
keyboard( unsigned char key, int x, int y )
{
	int index;
    switch( key ) {
	case 033: // Escape Key
	case 'q': case 'Q':
	    exit( EXIT_SUCCESS );
	    break;
	case 'O':
	case 'o'://original
		rebind();
		glUseProgram( programs[IMG::ORIGINAL] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'L':
	case 'l'://Luminance effect
		rebind();
		glUseProgram( programs[IMG::LUMINANCE] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'N':
	case 'n'://negative
		rebind();
		glUseProgram( programs[IMG::NEGATIVE] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'D':
	case 'd'://edge detection effect
		rebind();
		glUseProgram( programs[IMG::DETECTION] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'E'://embossing
	case 'e':
		rebind();
		glUseProgram( programs[IMG::EMBOSSING] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'T':
	case 't'://Toon rendering
		rebind();
		glUseProgram( programs[IMG::TOON] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'W':
	case 'w'://Twirl
		rebind();
		glUseProgram( programs[IMG::TWIRL] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'P':
	case 'p'://Ripple 
		rebind();
		glUseProgram( programs[IMG::RIPPLE] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
	case 'S':
	case 's'://spherical
		rebind();
		glUseProgram( programs[IMG::SPHERICAL] );
		glClear( GL_COLOR_BUFFER_BIT );
		glDrawArrays( GL_TRIANGLES, 0, 6 );
		glutSwapBuffers();
		break;
		
    }
}

//----------------------------------------------------------------------------

void
display()
{
	if(!initDisplay)
    {
		glClear( GL_COLOR_BUFFER_BIT );
		initDisplay = true;
		glutSwapBuffers();
	}
    //glDrawArrays( GL_TRIANGLES, 0, 6 );
    
}

//----------------------------------------------------------------------------

int
main( int argc, char *argv[] )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
    glutInitWindowSize( N, M );
    glutInitContextVersion( 3, 3 );
//    glutInitContextProfile( GLUT_CORE_PROFILE );
    glutCreateWindow( "Image Manipulation" );

	glewExperimental = GL_TRUE;

    glewInit();

    init();

    glutDisplayFunc( display );
    //glutReshapeFunc( reshape );
    glutKeyboardFunc( keyboard );

    glutMainLoop();
    return 0;
}
